import QuantLib as ql
today = ql.Date(16, ql.December, 2025)
ql.Settings.instance().evaluationDate = todayInterest-rate indexes
In previous notebooks, I used a few interest-rate indexes and some of their features without going into a lot of details about them. Let’s remedy that.
QuantLib provides classes for quite a few interest-rate indexes; for instance, SOFR (an overnight index) or Euribor, actually a family of indexes with different tenors.
sofr = ql.Sofr()
euribor6m = ql.Euribor(ql.Period(6, ql.Months))In the above, I passed the specific tenor I wanted to the Euribor constructor; but I could have instantiated the convenience class ql.Euribor6m. A look at the library will show you other overnight risk-free rates such as Estr, Sonia or Aonia, and other IBOR-like indexes such as Tibor, the soon-to-be-retired Jibar, or even already retired LIBOR rates.
Retrieving index fixings
Indexes as instantiated above are only good for showing you their conventions:
print(sofr.dayCounter())
print(euribor6m.fixingCalendar())Actual/360 day counter
TARGET calendar
You really want indexes to give you their fixings, though, and in their default state they can’t do that. The way to enable them depends on the kind of fixing you want.
Forecasting future fixings
In order to ask indexes for future fixings, we need to provide them a forecasting term structure; if we don’t, they will helpfully tell us.
try:
euribor6m.fixing(today + ql.Period(3, ql.Months))
except Exception as e:
print(e)null term structure set to this instance of Euribor6M Actual/360
I’ve shown in other notebooks how to bootstrap an interest-rate curve based on actual market data; for brevity, I’ll just use flat rates here. Different instances of the same index can be given different term structures; this can be useful if we want to compare different models by instantiating two copies of the same instrument.
euribor6m = ql.Euribor(
ql.Period(6, ql.Months),
ql.YieldTermStructureHandle(
ql.FlatForward(today, 0.04, ql.Actual360())
),
)
euribor6m_2 = ql.Euribor(
ql.Period(6, ql.Months),
ql.YieldTermStructureHandle(
ql.FlatForward(today, 0.045, ql.Actual360())
),
)print(euribor6m.fixing(today + ql.Period(3, ql.Months)))
print(euribor6m_2.fixing(today + ql.Period(3, ql.Months)))0.040411689691501405
0.045521490418468744
(At this point, you might also be thinking that in other notebooks we used indexes without a curve sometimes, for instance when bootstrapping a curve. That’s because the index wasn’t left without a curve during the calculation; it was associated internally to the curve being bootstrapped.)
Storing and retrieving past fixings
Curves allow us to forecast future fixings, but they can’t help with past dates:
sofr = ql.Sofr(
ql.YieldTermStructureHandle(
ql.FlatForward(today, 0.03, ql.Actual360())
)
)try:
sofr.fixing(ql.Date(20, ql.November, 2025))
except Exception as e:
print(e)Missing SOFRON Actual/360 fixing for November 20th, 2025
There are no models about past fixings; they are known, and all we should do is store them somewhere we can look them up. We can do that via the addFixing method:
sofr.addFixing(ql.Date(20, ql.November, 2025), 0.0391)sofr.fixing(ql.Date(20, ql.November, 2025))0.0391
Indexes also provide a plural addFixings method that allows one to store multiple fixings in one call. In fact, for real applications I usually don’t bother determining what fixings are needed for a given instrument; I retrieve from a DB the whole history for the period I’m interested in, I store it, and let the instruments and the indexes figure it out.
Unlike what we’ve seen for curves, different instances of the same index share past fixings:
sofr_1 = ql.Sofr()
sofr_1.fixing(ql.Date(20, ql.November, 2025))0.0391
This is because, as I mentioned, there are no multiple models or possibilities about it: if we know a fixing happened, it applies to that index regardless of what instance we’re using.
Custom indexes
The library provides quite a few indexes, but not all of them—it’s a wide world out there. If you need an index that’s not already available, you can create it using a couple of generic classes. For overnight indexes, the class to use is OvernightIndex, whose constructor is declared as
OvernightIndex(const std::string& name,
Natural settlementDays,
const Currency& currency,
const Calendar& fixingCalendar,
const DayCounter& dayCounter,
const Handle<YieldTermStructure>& h = {});
For instance, if you want to use the South-African ZARONIA index, you can create it as:
zaronia = ql.OvernightIndex(
"ZARONIA", 0, ql.ZARCurrency(), ql.SouthAfrica(), ql.Actual365Fixed()
)Such an index works as the built-in ones; in fact, the built-in classes do the same thing, since they inherit from this one and hard-code the parameters. Past fixings are shared between instances of the same index, where the library considers “the same index” two objects to which we passed the same index name:
zaronia.addFixing(ql.Date(12, ql.December, 2025), 0.06612)zaronia_1 = ql.OvernightIndex(
"ZARONIA", 0, ql.ZARCurrency(), ql.SouthAfrica(), ql.Actual365Fixed()
)
print(zaronia_1.fixing(ql.Date(12, ql.December, 2025)))0.06612
Unfortunately, this means that it’s possible to create two instances with the same name and different conventions. Don’t do that, we’re all responsible adults here.
For IBOR-like indexes, you can use the generic IborIndex class; its constructor requires a few more parameters as well as a tenor.
IborIndex(const std::string& familyName,
const Period& tenor,
Natural settlementDays,
const Currency& currency,
const Calendar& fixingCalendar,
BusinessDayConvention convention,
bool endOfMonth,
const DayCounter& dayCounter,
Handle<YieldTermStructure> h = {});
One Norwegian three-months NIBOR? Coming right up:
nibor3m = ql.IborIndex(
"NIBOR",
ql.Period(3, ql.Months),
2,
ql.NOKCurrency(),
ql.Norway(),
ql.ModifiedFollowing,
True,
ql.Actual360(),
)As you might expect, in this case “the same index” means that two instances share the same name and tenor.